#include "dxdiag_res.h"
#include "dxdiagcom.h"

#include "wtypes.h"
#include "wine/debug.h"
#include "wine/unicode.h"

WINE_DEFAULT_DEBUG_CHANNEL(dxdiag);

/* Input tab callback proceedure */
LRESULT CALLBACK Input_DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{

    HBRUSH g_hbrBackground = CreateSolidBrush(RGB(255,255,255));
    switch(uMsg) {
        case WM_INITDIALOG:
            return TRUE;
        case WM_CTLCOLORSTATIC:
            if (GetDlgCtrlID((HWND) lParam) == INPUTTAB_NOTESEDIT) {
                return (LRESULT)g_hbrBackground;
            }
        default:
            break;
    }

    return FALSE;
}

void InputInfo_SaveText(FILE* pFile)
{
    /* Fixme: Unimplemented */
}

InputDeviceInfo* getInputDeviceInfo(IDxDiagContainer* deviceCollection, DWORD* numOfDevices)
{
    /* Get information about the device collection */
    HRESULT hr;
    IDxDiagContainer* device;
    IDxDiagContainer* deviceDrivers;
    IDxDiagContainer* deviceDriverFile;
    UINT i, j;
    hr = IDxDiagContainer_GetNumberOfChildContainers(deviceCollection, numOfDevices);
    if(FAILED(hr))
    {
        WINE_ERR("IDxDiagContainer_GetNumberOfChildContainers failed\n");
        return NULL;
    }

    else
    {
        InputDeviceInfo* deviceCollectionInfo = (InputDeviceInfo*)malloc(sizeof(InputDeviceInfo) * (*numOfDevices));
        if(deviceCollectionInfo != NULL)
        {
            for(i = 0; i < (*numOfDevices); i++)
            {
                static const WCHAR fmt[] = { '%', 'd', '\0' };
                LPWSTR deviceNumber = (LPWSTR)calloc(21, sizeof(WCHAR));
                sprintfW(deviceNumber, fmt, i);
                hr = DxDiagCOM_GetContainer(deviceCollection, deviceNumber, &device);

                if(!FAILED(hr))
                {
                    /* Get information about individual devices */
                    DxDiagCOM_GetStringProperty(device, DxDiag_LoadString(INPUT_SZDESCRIPTION), &deviceCollectionInfo[i].deviceName);
                    DxDiagCOM_GetStringProperty(device, DxDiag_LoadString(INPUT_SZUPPERFILTERS), &deviceCollectionInfo[i].upperFilters);
                    DxDiagCOM_GetStringProperty(device, DxDiag_LoadString(INPUT_SZSERVICE), &deviceCollectionInfo[i].serviceName);
                    DxDiagCOM_GetStringProperty(device, DxDiag_LoadString(INPUT_SZMATCHINGDEVICEID), &deviceCollectionInfo[i].deviceID);
                    DxDiagCOM_GetDwordProperty(device, DxDiag_LoadString(INPUT_DWVENDORID), &deviceCollectionInfo[i].vendorID);
                    DxDiagCOM_GetDwordProperty(device, DxDiag_LoadString(INPUT_DWPRODUCTID), &deviceCollectionInfo[i].productID);

                    /* Build list of driver files*/
                    DxDiagCOM_GetContainer(device, "Drivers", &deviceDrivers);
                    IDxDiagContainer_GetNumberOfChildContainers(deviceDrivers, &deviceCollectionInfo[i].numOfDriverFiles);
                    deviceCollectionInfo[i].driverFiles = (DriverFileInfo*)malloc(sizeof(DriverFileInfo) *
                        deviceCollectionInfo[i].numOfDriverFiles);

                    for(j = 0; j < deviceCollectionInfo[i].numOfDriverFiles; j++)
                    {
                        LPWSTR driverNumber = (LPWSTR)calloc(21, sizeof(WCHAR));
                        sprintfW(driverNumber, "%d", j);
                        DxDiagCOM_GetContainer(deviceDrivers, driverNumber, &deviceDriverFile);
                        DxDiagCOM_GetStringProperty(deviceDriverFile, DxDiag_LoadString(INPUT_SZNAME), &deviceCollectionInfo[i].driverFiles[j].fileName);
                        DxDiagCOM_GetStringProperty(deviceDriverFile, DxDiag_LoadString(INPUT_SZDATESTAMPLOCALIZED), &deviceCollectionInfo[i].driverFiles[j].time);
                        DxDiagCOM_GetIntegerProperty(deviceDriverFile, DxDiag_LoadString(INPUT_LNUMBYTES), &deviceCollectionInfo[i].driverFiles[j].fileSize);
                        free(driverNumber);
                    }
                }
                free(deviceNumber);
            }
        }
        return deviceCollectionInfo;
    }
}

BOOL InputInfo_GetInfo(DxDiagCOM* pcom, InputInfo* info)
{
    IDxDiagContainer* input;
    IDxDiagContainer* directInput;
    IDxDiagContainer* directInputDevice;
    IDxDiagContainer* ps2Devices;
    IDxDiagContainer* USBDevices;
    IDxDiagContainer* gameportDevices;
    UINT i;

    /* Get the input info container */
    if (FAILED(DxDiagCOM_GetContainer(pcom->root, "DxDiag_DirectInput", &input)))
        return FALSE;

    /* Get notes */
    DxDiagCOM_GetStringProperty(input, DxDiag_LoadString(INPUT_SZINPUTNOTESENGLISH),&info->notes);

    /* Get the polling w/ interrupt property */
    DxDiagCOM_GetBoolProperty(input, DxDiag_LoadString(INPUT_BPOLLFLAGS), &info->pollInterrupt);

    DxDiagCOM_GetContainer(input, "DxDiag_DirectInputDevices", &directInput);
    /* TODO: error checking on all DxDiagCOM calls */

    /* Get information about DirectInput devices*/
    IDxDiagContainer_GetNumberOfChildContainers(directInput, &info->numOfDIDevices);
    info->DIDInfo = (DIDeviceInfo*)malloc(sizeof(DIDeviceInfo) * info->numOfDIDevices);
    if(info->DIDInfo != NULL)
    {
        for(i = 0; i < info->numOfDIDevices; i++)
        {
            static const WCHAR fmt[] = { '%', 'd', '\0' };
            /* 21 characters are required to represent numbers up to 2^64 (includes terminating null byte) */
            LPWSTR deviceNumber = (LPWSTR)calloc(21, sizeof(WCHAR));
            sprintfW(deviceNumber, fmt, i);
            DxDiagCOM_GetContainer(directInput, deviceNumber, &directInputDevice);

            /* Not sure how to get controller ID, so just set it to zero for now */
            info->DIDInfo[i].controllerID = 0;
            DxDiagCOM_GetStringProperty(directInputDevice, DxDiag_LoadString(INPUT_SZINSTANCENAME), &info->DIDInfo[i].deviceName);
            DxDiagCOM_GetStringProperty(directInputDevice, DxDiag_LoadString(INPUT_SZFFDRIVERNAME), &info->DIDInfo[i].ffDriver);
            DxDiagCOM_GetBoolProperty(directInputDevice, DxDiag_LoadString(INPUT_BATTACHED), &info->DIDInfo[i].isAttached);
            DxDiagCOM_GetDwordProperty(directInputDevice, DxDiag_LoadString(INPUT_DWVENDORID), &info->DIDInfo[i].vendorID);
            DxDiagCOM_GetDwordProperty(directInputDevice, DxDiag_LoadString(INPUT_DWPRODUCTID), &info->DIDInfo[i].productID);
            free(deviceNumber);
        }
    }

    /*Get information about Gameport, USB, and PS/2 devices */
    DxDiagCOM_GetContainer(input, "DxDiag_DirectInputGameports", &gameportDevices);
    info->GameportInfo = getInputDeviceInfo(gameportDevices, &info->numOfGameDevices);
    DxDiagCOM_GetContainer(input, "DxDiag_DirectInputUSBRoot", &USBDevices);
    info->USBInfo = getInputDeviceInfo(USBDevices, &info->numOfUSBDevices);
    DxDiagCOM_GetContainer(input, "DxDiag_DirectInputPS2Devices", &ps2Devices);
    info->PS2Info = getInputDeviceInfo(ps2Devices, &info->numOfPS2Devices);

    return TRUE;
}

void displayInputDeviceInfo(HWND hTree, InputDeviceInfo* inputDevices, DWORD numOfDevices, HTREEITEM parent)
{
    UINT i,j;
    TVINSERTSTRUCT tvis;
    TVITEM tvi;
    HTREEITEM currentItem;
    HTREEITEM dummyItem;
    LPWSTR query = (LPWSTR)malloc(BUF_SIZE * sizeof(WCHAR));

    memset((void*)&tvi, 0, sizeof(TVITEM));

    tvi.mask = TVIF_TEXT;
    tvi.state = 0;
    tvi.stateMask = 0;
    tvi.lParam = (LPARAM) 0;

    for(i = 0; i < inputInfo.numOfGameDevices; i++)
    {
        tvi.pszText = inputDevices[i].deviceName;
        tvis.hParent = parent;
        tvis.hInsertAfter = TVI_LAST;
        tvis.item = tvi;
        currentItem = TreeView_InsertItem(hTree, &tvis);

        tvis.hParent = currentItem;
        if(inputDevices[i].vendorID != 0 && inputDevices[i].productID != 0)
        {
            sprintfW(query, "Vendor/Product ID: 0x%X, 0x%X\n", inputDevices[i].vendorID, inputDevices[i].productID);
            tvi.pszText = query;
            tvis.item = tvi;
            dummyItem = TreeView_InsertItem(hTree, &tvis);
        }

        if(wcscmp(inputDevices[i].deviceID, "") != 0)
        {
            sprintfW(query, "Matching Device ID: %s\n", inputDevices[i].deviceID);
            tvi.pszText = query;
            tvis.item = tvi;
            dummyItem = TreeView_InsertItem(hTree, &tvis);
        }

        if(wcscmp(inputDevices[i].upperFilters, "") != 0)
        {
            sprintfW(query, "Upper Filters: %s\n", inputDevices[i].upperFilters);
            tvi.pszText = query;
            tvis.item = tvi;
            dummyItem = TreeView_InsertItem(hTree, &tvis);
        }

        if(wcscmp(inputDevices[i].serviceName, "") != 0)
        {
            sprintfW(query, "Service: %s\n", inputDevices[i].serviceName);
            tvi.pszText = query;
            tvis.item = tvi;
            dummyItem = TreeView_InsertItem(hTree, &tvis);
        }

        for(j = 0; j < inputDevices[i].numOfDriverFiles; j++)
        {
            sprintfW(query, "Driver: %s, %s\n", inputDevices[i].driverFiles[j].fileName, inputDevices[i].driverFiles[j].time);
            tvi.pszText = query;
            tvis.item = tvi;
            dummyItem = TreeView_InsertItem(hTree, &tvis);
        }
    }
}

void Input_DisplayData(void)
{
    LVCOLUMN lvc;
    LVITEM lvi;
    TVINSERTSTRUCT tvis;
    TVITEM tvi;
    int result;
    UINT i;

    LPWSTR query = (LPWSTR)malloc(BUF_SIZE * sizeof(WCHAR));
    HWND hList = GetDlgItem(hTabDisplay, INPUTTAB_DEVICESLIST);
    HWND hTree = GetDlgItem(hTabDisplay, INPUTTAB_DEVICESTREE);
    HWND hNotes = GetDlgItem(hTabDisplay, INPUTTAB_NOTESEDIT);
    HTREEITEM currentItem, dummyItem;

    /* Set notes */
    SendMessage(hNotes,WM_SETTEXT,0,(LPARAM)inputInfo.notes);

    memset((void*)&tvi, 0, sizeof(TVITEM));

    /* Add columns */
    lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
    lvc.cx = 100;

    lvc.iSubItem = 0;
    lvc.pszText = (LPWSTR)"Device Name";
    ListView_InsertColumn(hList, 0, &lvc);
    lvc.iSubItem = 0;
    lvc.pszText = (LPWSTR)"Status";
    ListView_InsertColumn(hList, 1, &lvc);
    lvc.iSubItem = 0;
    lvc.pszText = (LPWSTR)"Controller ID";
    ListView_InsertColumn(hList, 2, &lvc);
    lvc.iSubItem = 0;
    lvc.pszText = (LPWSTR)"Vendor ID";
    ListView_InsertColumn(hList, 3, &lvc);
    lvc.iSubItem = 0;
    lvc.pszText = (LPWSTR)"Product ID";
    ListView_InsertColumn(hList, 4, &lvc);
    lvc.iSubItem = 0;
    lvc.pszText = (LPWSTR)"Force Feedback Driver";
    ListView_InsertColumn(hList, 5, &lvc);


    /* Add each DirectInput device to the list */
    for(i = 0; i < inputInfo.numOfDIDevices; i++)
    {
        lvi.mask = LVIF_TEXT;
        lvi.state = 0;
        lvi.stateMask = 0;
        lvi.iItem = i;
        lvi.iSubItem = 0;
        lvi.lParam = (LPARAM) 0;
        /* lvi.pszText = LPSTREXTCALLBACK; */
        lvi.pszText = inputInfo.DIDInfo[i].deviceName;
        result = ListView_InsertItem(hList, &lvi);
        if (result < 0)
            WINE_ERR("ListView::InsertItem() - failed to insert item!\n");

        if(inputInfo.DIDInfo[i].isAttached == TRUE)
        {
            ListView_SetItemText(hList, i, 1, (LPWSTR)"Attached");
        }
        else
        {
            ListView_SetItemText(hList, i, 1, (LPWSTR)"Not attached");
        }

        if(inputInfo.DIDInfo[i].controllerID == 0)
        {
            ListView_SetItemText(hList, i, 2, (LPWSTR)"n/a");
        }
        else
        {
            ListView_SetItemText(hList, i, 2, (LPWSTR)"n/a");
            /* TODO: get controllerID field */
        }

        if(inputInfo.DIDInfo[i].vendorID == 0)
        {
            ListView_SetItemText(hList, i, 3, (LPWSTR)"n/a");
        }
        else
        {
            sprintfW(query, "0x%X", inputInfo.DIDInfo[i].vendorID);
            ListView_SetItemText(hList, i, 3, (LPWSTR)query);
        }

        if(inputInfo.DIDInfo[i].productID == 0)
        {
            ListView_SetItemText(hList, i, 4, (LPWSTR)"n/a");
        }
        else
        {
            sprintfW(query, "0x%X", inputInfo.DIDInfo[i].productID);
            ListView_SetItemText(hList, i, 4, query);
        }

        if(wcscmp(inputInfo.DIDInfo[i].ffDriver, "") == 0)
        {
            ListView_SetItemText(hList, i, 5, (LPWSTR)"n/a");
        }
        else
        {
            ListView_SetItemText(hList, i, 5, inputInfo.DIDInfo[i].ffDriver);
        }
    }

    tvi.mask = TVIF_TEXT;
    tvi.state = 0;
    tvi.stateMask = 0;
    tvi.lParam = (LPARAM) 0;
    tvis.hParent = TVI_ROOT;
    tvis.hInsertAfter = TVI_LAST;
    if(inputInfo.numOfUSBDevices > 0)
    {
        tvi.pszText = (LPWSTR)"USB Devices";
        tvis.item = tvi;
        currentItem = TreeView_InsertItem(hTree, &tvis);
        displayInputDeviceInfo(hTree, inputInfo.USBInfo, inputInfo.numOfUSBDevices, currentItem);
    }

    if(inputInfo.numOfGameDevices > 0)
    {
        tvi.pszText = (LPWSTR)"Gameport Devices";
        tvis.item = tvi;
        currentItem = TreeView_InsertItem(hTree, &tvis);
        displayInputDeviceInfo(hTree, inputInfo.GameportInfo, inputInfo.numOfGameDevices, currentItem);
    }

    if(inputInfo.numOfPS2Devices > 0)
    {
        tvi.pszText = (LPWSTR)"PS/2 Devices";
        tvis.item = tvi;
        currentItem = TreeView_InsertItem(hTree, &tvis);
        displayInputDeviceInfo(hTree, inputInfo.PS2Info, inputInfo.numOfPS2Devices, currentItem);
    }

    if(inputInfo.numOfUSBDevices == 0 && inputInfo.numOfPS2Devices == 0 && inputInfo.numOfUSBDevices == 0)
    {
        tvi.pszText = (LPWSTR)"n/a";
        tvis.item = tvi;
        dummyItem = TreeView_InsertItem(hTree, &tvis);
    }
    free(query);
}

void printInputDeviceXML(XML_Node* root, InputDeviceInfo* inputDevices, DWORD numOfDevices)
{
    WCHAR buf[BUF_SIZE];
    XML_Node* driversXML;
    UINT i,j;
    for (i = 0; i < numOfDevices; i++)
    {
        XML_Node* reldevXML = XML_insertChildNode(root, DxDiag_LoadString(INPUT_XML_INPUTRELATEDDEVICE), NULL);
        XML_insertChildNode(reldevXML, DxDiag_LoadString(INPUT_XML_DESCRIPTION), inputDevices[i].deviceName);
        sprintfW(buf,("0x%X"), inputDevices[i].vendorID);
        XML_insertChildNode(reldevXML, DxDiag_LoadString(INPUT_XML_VENDORID),buf);
        sprintfW(buf,("0x%X"), inputDevices[i].productID);
        XML_insertChildNode(reldevXML, DxDiag_LoadString(INPUT_XML_PRODUCTID),buf);
        XML_insertChildNode(reldevXML, DxDiag_LoadString(INPUT_XML_LOCATION),NULL);
        XML_insertChildNode(reldevXML, DxDiag_LoadString(INPUT_XML_MATCHINGDEVICEID),inputDevices[i].deviceID);
        XML_insertChildNode(reldevXML, DxDiag_LoadString(INPUT_XML_UPPERFILTERS),NULL);
        XML_insertChildNode(reldevXML, DxDiag_LoadString(INPUT_XML_LOWERFILTERS),NULL);
        XML_insertChildNode(reldevXML, DxDiag_LoadString(INPUT_XML_SERVICE),NULL);
        driversXML = XML_insertChildNode(reldevXML, DxDiag_LoadString(INPUT_XML_DRIVERS), NULL);

        for (j = 0; j < inputDevices[i].numOfDriverFiles; j++)
        {
            XML_Node* driverXML = XML_insertChildNode(driversXML, DxDiag_LoadString(INPUT_XML_DRIVER), NULL);
            XML_insertChildNode(driverXML, DxDiag_LoadString(INPUT_XML_NAME),inputDevices[i].driverFiles[j].fileName);
            XML_insertChildNode(driverXML, DxDiag_LoadString(INPUT_XML_PATH),NULL);
            XML_insertChildNode(driverXML, DxDiag_LoadString(INPUT_XML_VERSION),NULL);
            XML_insertChildNode(driverXML, DxDiag_LoadString(INPUT_XML_LANGUAGE),NULL);
            XML_insertChildNode(driverXML, DxDiag_LoadString(INPUT_XML_BETA),NULL);
            XML_insertChildNode(driverXML, DxDiag_LoadString(INPUT_XML_DEBUG),NULL);
            XML_insertChildNode(driverXML, DxDiag_LoadString(INPUT_XML_DATE),inputDevices[i].driverFiles[j].time);
            XML_insertChildNode(driverXML, DxDiag_LoadString(INPUT_XML_SIZE),NULL);
        }

    }
}

void printInputDeviceInfo(FILE* pFile, InputDeviceInfo* inputDevices, DWORD numOfDevices)
{
    /* XXX fixme: not implemented */
}

void Input_SaveText(FILE* pFile)
{
    /* XXX fixme: not implemented */
}

void Input_SaveXML(XML_Node* root)
{
    WCHAR buf[BUF_SIZE];
    UINT i;
    XML_Node* inputDevicesXML = XML_insertChildNode(root, DxDiag_LoadString(INPUT_XML_DIRECTINPUTDEVICES), NULL);
    XML_Node* USBRootXML = XML_insertChildNode(root, DxDiag_LoadString(INPUT_XML_USBROOT), NULL);
    XML_Node* GamePortDevicesXML = XML_insertChildNode(root, DxDiag_LoadString(INPUT_XML_GAMEPORTDEVICES), NULL);
    XML_Node* PS2DevicesXML = XML_insertChildNode(root, DxDiag_LoadString(INPUT_XML_PS2DEVICES), NULL);
    XML_insertChildNode(root, DxDiag_LoadString(INPUT_XML_POLLWITHINTERRUPT), NULL);
    XML_insertChildNode(root, DxDiag_LoadString(INPUT_XML_REGISTRY), NULL);

    for (i = 0; i < inputInfo.numOfDIDevices; i++)
    {
        XML_Node* inputDeviceXML = XML_insertChildNode(inputDevicesXML, DxDiag_LoadString(INPUT_XML_DIRECTINPUTDEVICE), NULL);
        XML_insertChildNode(inputDeviceXML, DxDiag_LoadString(INPUT_XML_DEVICENAME),inputInfo.DIDInfo[i].deviceName);
        sprintfW(buf,("%d"),inputInfo.DIDInfo[i].isAttached);
        XML_insertChildNode(inputDeviceXML, DxDiag_LoadString(INPUT_XML_ATTACHED),buf);
        sprintfW(buf,("0x%X"),inputInfo.DIDInfo[i].controllerID);
        XML_insertChildNode(inputDeviceXML, DxDiag_LoadString(INPUT_XML_JOYSTICKID), buf);
        sprintfW(buf,("0x%X"),inputInfo.DIDInfo[i].vendorID);
        XML_insertChildNode(inputDeviceXML, "VendorID",buf);
        XML_insertChildNode(inputDeviceXML, DxDiag_LoadString(INPUT_XML_DEVTYPE),NULL);
        XML_insertChildNode(inputDeviceXML, DxDiag_LoadString(INPUT_XML_FFDRIVERNAME),inputInfo.DIDInfo[i].ffDriver);
        XML_insertChildNode(inputDeviceXML, DxDiag_LoadString(INPUT_XML_FFDRIVERDATEENGLISH),NULL);
        XML_insertChildNode(inputDeviceXML, DxDiag_LoadString(INPUT_XML_FFDRIVERVERSION),NULL);
        XML_insertChildNode(inputDeviceXML, DxDiag_LoadString(INPUT_XML_FFDRIVERSIZE),NULL);
    }

    printInputDeviceXML(USBRootXML, inputInfo.USBInfo, inputInfo.numOfUSBDevices);
    printInputDeviceXML(PS2DevicesXML, inputInfo.GameportInfo, inputInfo.numOfGameDevices);
    printInputDeviceXML(GamePortDevicesXML, inputInfo.USBInfo, inputInfo.numOfGameDevices);

}
